#ifndef LISTA_H
#define LISTA_H

#include <iostream>
#include "Nodo.h"
#include "ExcepcionIndiceFueraRango.h"
#include "ExcepcionProceso.h"
#include "ExcepcionListaVacia.h"
using namespace std;

/**
 * Template que representa una lista doblemente enlazada
 */
template<class T>
class Lista {
public:
	/**
	 * Constructor
	 */
	Lista() : plista(NULL) {}

	/**
	 * Destructor
	 */
	~Lista();

	/**
	 * Métodos públicos para interacturar con la lista de nodos
	 */
	void insertar(int pos, T v);
	void modificar(int pos, T v);
	void borrar(int pos);
	void mostrar();
	int getLongitud();

private:
	/**
	 * Lista de nodos
	 */
	Nodo<T> *plista;

	/**
	 * Método privados utilizados desde los métodos públicos
	 * para interactuar con la lista de nodos
	 */
	bool isListaVacia();
	void siguiente();
	void anterior();
	void primero();
};

/**
 * Destructor
 */
template<class T>
Lista<T>::~Lista() {
	try {
		Nodo<T> *aux;

		primero();
		while (plista) {
			aux = plista;
			plista = plista->siguiente;
			delete aux;
		}
	} catch (...) {
		throw ExcepcionProceso("Destructor");
	}

}

/**
 * Inserta un nodo con el valor "v" en la lista
 * en la posición recibida
 */
template<class T>
void Lista<T>::insertar(int pos, T v) {

	try {
		primero();
		int longitud = getLongitud();

		if (pos <= longitud && pos >= 0) {
			Nodo<T> *nuevo;

			// Si la lista está vacía
			if (isListaVacia()) {
				// Asignamos a lista un nuevo nodo de valor v y
				// cuyo siguiente elemento es la lista actual
				nuevo = new Nodo<T>(v, plista);
				if (!plista)
					plista = nuevo;
				else
					plista->anterior = nuevo;
			} else {
				// Buscar el nodo de valor menor a v
				// Avanzamos hasta el último elemento o hasta que el siguiente tenga
				// un valor mayor que v
				int recorrido = 1;
				while (recorrido++ < pos && plista->siguiente)
					siguiente();

				if (pos == 0) {
					nuevo = new Nodo<T>(v, plista, NULL);
				} else {
					nuevo = new Nodo<T>(v, plista->siguiente, plista);
					plista->siguiente = nuevo;
				}

				if (nuevo->siguiente) {
					nuevo->siguiente->anterior = nuevo;
				}
			}
		} else {
			throw ExcepcionIndiceFueraRango("Insertar");
		}

	} catch (ExcepcionIndiceFueraRango &e) {
		throw;
	} catch (...) {
		throw ExcepcionProceso("Insertar");
	}
}

/**
 * Modifica con el valor "v" el nodo de la lista
 * que corresponde a la posición recibida
 */
template<class T>
void Lista<T>::modificar(int pos, T v) {

	try {
		primero();
		int longitud = getLongitud();

		if (pos < longitud && pos >= 0) {
			int recorrido = 0;
			while (recorrido++ < pos && plista->siguiente) siguiente();

		    plista->valor = v;

		} else {
			throw ExcepcionIndiceFueraRango("Modificar");
		}
	} catch (ExcepcionIndiceFueraRango &e) {
		throw;
	} catch (...) {
		throw ExcepcionProceso("Modificar");
	}
}

/**
 * Borra el nodo de la lista cuya posición se recibe
 */
template<class T>
void Lista<T>::borrar(int pos) {
	try {
		Nodo<T> *pnodo;

		// Nos situamos al comienzo de la lista
		primero();
		pnodo = plista;
		int recorrido = 0;

		// Nos situamos en el nodo anterior al que queremos borrar
		while (pnodo && recorrido < pos){
			pnodo = pnodo->siguiente;
			recorrido++;
		}

		if (!pnodo)
			throw ExcepcionIndiceFueraRango("Borrar");

		// Borrar el nodo
		if (pnodo->anterior) { // no es el primer elemento
			pnodo->anterior->siguiente = pnodo->siguiente;
		}
		if (pnodo->siguiente) { // no es el último nodo
			pnodo->siguiente->anterior = pnodo->anterior;
			if (!pnodo->anterior) { // si es el primer elemento
				// apuntamos el inicio de la lista al nodo siguiente
				plista = pnodo->siguiente;
			}
		}

		// Liberamos de memoria el nodo eliminado
		delete pnodo;
	} catch (ExcepcionIndiceFueraRango &e) {
		throw;
	} catch (...) {
		throw ExcepcionProceso("Borrar");
	}
}

/**
 * Muestra en orden ascendente (desde el primer elemento
 * al último) la lista de nodos
 */
template<class T>
void Lista<T>::mostrar() {
	try {
		if (!isListaVacia()) {
			Nodo<T> *pnodo;

			primero();
			pnodo = plista;

			cout << "Lista: " << endl;
			while (pnodo) {
				cout << "   " << pnodo->valor.toString() << "-> " << endl;
				pnodo = pnodo->siguiente;
			}

			cout << endl;
		} else {
			throw ExcepcionListaVacia("Mostrar");
		}
	} catch (ExcepcionListaVacia &e) {
		throw;
	} catch (...) {
		throw ExcepcionProceso("Mostrar");
	}

}

/**
 * La lista apunta al siguiente nodo
 */
template<class T>
void Lista<T>::siguiente() {
	if (plista)
		plista = plista->siguiente;
}

/**
 * La lista apunta al nodo anterior
 */
template<class T>
void Lista<T>::anterior() {
	if (plista)
		plista = plista->anterior;
}

/**
 * La lista apunta al primer nodo
 */
template<class T>
void Lista<T>::primero() {
	while (plista && plista->anterior)
		anterior();
}

/**
 * Comprueba si la Lista está vacía (no tiene ningún nodo)
 */
template<class T>
bool Lista<T>::isListaVacia() {
	return plista == NULL;
}

/**
 * Devuelve el tamaño de la lista, es decir, el
 * número de nodos de la lista
 */
template<class T>
int Lista<T>::getLongitud() {
	int contador = 0;
	try {
		Nodo<T> *pnodo = plista;

		if (!isListaVacia()) {
			pnodo = plista;

			while (pnodo) {
				pnodo = pnodo->siguiente;
				contador++;
			}
		}
	} catch (...) {
		throw ExcepcionProceso("getLongitud");
	}

	return contador;
}

#endif
